@matops/editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # @matops/editor
2
+
3
+ > Production-grade, modular rich text editor for the Matops ecosystem.
4
+ > Built on **Tiptap v3** · **React 18** · **TypeScript 5**
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ `@matops/editor` is a **fully standalone, backend-agnostic** editor package.
11
+ It exposes a clean props interface and connects to your infrastructure via callbacks — zero backend code inside.
12
+
13
+ ```tsx
14
+ <Editor
15
+ features={["formatting", "code", "tables", "media"]}
16
+ theme="dark"
17
+ editable={true}
18
+ onChange={handleChange}
19
+ onPublish={handlePublish}
20
+ onFileUploaded={handleFileUpload}
21
+ />
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ # In a monorepo — reference the workspace package:
30
+ "dependencies": {
31
+ "@matops/editor": "workspace:*"
32
+ }
33
+
34
+ # Or as a private npm package:
35
+ npm install @matops/editor
36
+ ```
37
+
38
+ **Requirements:** Node ≥ 18, React ≥ 18.
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ Enable capabilities declaratively via the `features` prop:
45
+
46
+ ```tsx
47
+ <Editor
48
+ features={[
49
+ "history", // Undo / redo
50
+ "formatting", // Bold, italic, headings, lists, blockquote…
51
+ "code", // Inline code + syntax-highlighted code blocks
52
+ "tables", // Full table editing
53
+ "media", // Images + links
54
+ "task-list", // Checkbox to-do lists
55
+ "placeholder", // Empty-state placeholder text
56
+ "character-count",// Live character / word count bar
57
+ "find-replace", // Find & replace panel (Ctrl+F)
58
+ "cover-image", // Full-width cover photo
59
+ "page-header", // Cover image + title + separator
60
+ "attachments", // File attachment list
61
+ "collaboration", // Real-time Yjs collaboration
62
+ ]}
63
+ />
64
+ ```
65
+
66
+ ### Automatic Dependency Resolution
67
+
68
+ The **Feature Registry** resolves dependencies transparently:
69
+
70
+ | Feature | Requires |
71
+ |---|---|
72
+ | `tables` | `formatting` |
73
+ | `code` | `formatting` |
74
+ | `collaboration` | disables `history` (Yjs handles undo) |
75
+
76
+ You never need to manually manage the dependency tree.
77
+
78
+ ---
79
+
80
+ ## Props
81
+
82
+ ```ts
83
+ interface EditorProps {
84
+ features?: EditorFeature[]
85
+ initialContent?: JSONContent // body-only — structural nodes injected automatically
86
+ theme?: "light" | "dark"
87
+ editable?: boolean
88
+ placeholder?: string
89
+ collaborationProvider?: CollaborationProvider
90
+ className?: string
91
+
92
+ // Callbacks — all backend integration happens here
93
+ onChange?: (json: JSONContent) => void
94
+ onContentChange?: (html: string, json: JSONContent) => void
95
+ onPublish?: (content: JSONContent) => void
96
+ onFileUploaded?: (file: File) => Promise<string>
97
+ onEditorReady?: (editor: TiptapEditor) => void
98
+ }
99
+ ```
100
+
101
+ ### `initialContent` is body-only
102
+
103
+ You do not need to include `cover-image`, `title`, or `attachments` nodes.
104
+ The Editor injects and manages those structural nodes automatically based on
105
+ the active feature set. Pass only your paragraph / heading / list content —
106
+ or omit `initialContent` entirely for a blank document.
107
+
108
+ ---
109
+
110
+ ## Viewer Mode
111
+
112
+ Render saved content without loading any editing UI:
113
+
114
+ ```tsx
115
+ import { EditorViewer } from "@matops/editor";
116
+
117
+ <EditorViewer content={savedJsonContent} theme="light" />
118
+ ```
119
+
120
+ Uses `@tiptap/html`'s `generateHTML` — works server-side too (SSR / Next.js).
121
+
122
+ ---
123
+
124
+ ## Collaboration
125
+
126
+ The editor is **100% backend-agnostic**. You bring the Yjs provider:
127
+
128
+ ```tsx
129
+ import * as Y from "yjs";
130
+ import { WebsocketProvider } from "y-websocket";
131
+
132
+ const ydoc = new Y.Doc();
133
+ const provider = new WebsocketProvider("ws://your-server", "room-id", ydoc);
134
+
135
+ <Editor
136
+ features={["formatting", "collaboration"]}
137
+ collaborationProvider={{
138
+ provider,
139
+ fragment: ydoc.getXmlFragment("default"),
140
+ user: { name: "Alice", color: "#6366f1" },
141
+ }}
142
+ />
143
+ ```
144
+
145
+ Compatible with `y-websocket`, `@hocuspocus/provider`, and any Yjs-compatible provider.
146
+
147
+ ---
148
+
149
+ ## Custom Feature Registration
150
+
151
+ Extend the editor with your own Tiptap extensions:
152
+
153
+ ```ts
154
+ import { registerFeature } from "@matops/editor";
155
+ import MyCustomExtension from "./MyCustomExtension";
156
+
157
+ registerFeature({
158
+ name: "my-feature", // add to EditorFeature union type in types.ts
159
+ extensions: [MyCustomExtension],
160
+ dependencies: ["formatting"], // pulled in automatically
161
+ enabled: (features) => features.includes("formatting"),
162
+ });
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Content Type Mapping
168
+
169
+ The editor has no opinion about your content model.
170
+ Map features at the application layer:
171
+
172
+ ```ts
173
+ const CONTENT_TYPE_FEATURES: Record<string, EditorFeature[]> = {
174
+ blog_post: ["history", "formatting", "code", "tables", "media"],
175
+ quick_update: ["history", "formatting"],
176
+ deep_dive: ["history", "formatting", "code", "tables", "media"],
177
+ };
178
+
179
+ <Editor features={CONTENT_TYPE_FEATURES[post.type]} />
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Architecture
185
+
186
+ ```
187
+ shared/editor
188
+ ├─ core/
189
+ │ ├─ Editor.tsx ← Public <Editor> + internal <EditorCore>
190
+ │ ├─ EditorProvider.tsx ← React context + all hooks
191
+ │ ├─ featureRegistry.ts ← Plugin registry (register / resolve)
192
+ │ ├─ editorConfigBuilder.ts ← Extension resolver + runtime injections
193
+ │ └─ types.ts ← TypeScript types (single source of truth)
194
+
195
+ ├─ extensions/
196
+ │ ├─ commands/ ← History, placeholder, character-count…
197
+ │ ├─ formatting/ ← Bold, italic, headings, lists…
198
+ │ ├─ media/ ← Images, links
199
+ │ ├─ tables/ ← Table editing
200
+ │ ├─ collaboration/ ← Yjs real-time
201
+ │ ├─ nodes/ ← CoverImageNode, TitleNode, AttachmentsNode…
202
+ │ └─ plugins/ ← Guard plugins, search plugin
203
+
204
+ ├─ ui/
205
+ │ ├─ toolbar/ ← EditorToolbar, EditorToolbarWithActions
206
+ │ ├─ bubble-menu/ ← EditorBubbleMenu, ImageBubbleMenu
207
+ │ ├─ floating-menu/ ← EditorFloatingMenu
208
+ │ ├─ slash-menu/ ← SlashCommandMenu
209
+ │ ├─ table-menu/ ← EditorTableMenu
210
+ │ ├─ link-menu/ ← LinkEditPopover
211
+ │ ├─ modals/ ← KeyboardShortcutsModal, FindReplacePanel, ExportPanel
212
+ │ ├─ components/ ← CharacterCountBar, TitleSeparator, CropModal
213
+ │ ├─ primitives/ ← MenuButton, MenuDivider, ModalOverlay…
214
+ │ └─ icons/ ← DefaultSeparatorIcon, UploadIcon
215
+
216
+ ├─ viewer/
217
+ │ └─ EditorViewer.tsx ← Read-only renderer
218
+
219
+ └─ utils/
220
+ ├─ hooks/ ← useEditorInstance, useEditorKeyboard…
221
+ ├─ helpers/ ← cn(), docHelpers, cropHelpers
222
+ └─ constants/ ← DEFAULT_FEATURES, theme classes, data attrs
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Package Versions
228
+
229
+ | Package | Version | Notes |
230
+ |---|---|---|
231
+ | `@tiptap/core` | `^3.0.0` | Latest 3.20.1 |
232
+ | `@tiptap/react` | `^3.0.0` | Latest 3.20.1 |
233
+ | `@tiptap/extension-*` | `^3.0.0` | Independent release cadence |
234
+ | `@tiptap/extension-collaboration-caret` | `^3.0.0` | ⚠️ Renamed from `collaboration-cursor` in v3 |
235
+ | `yjs` | `^13.6.27` | — |
236
+ | `y-websocket` | `^3.0.0` | — |
237
+ | `lowlight` | `^3.3.0` | — |
238
+ | `clsx` | `^2.1.1` | — |
239
+ | `tailwind-merge` | `^3.3.0` | — |
240
+
241
+ > **Why `^3.0.0` and not `^3.20.1`?** Tiptap core and individual extensions publish independently. Pinning to `^3.20.1` fails when any extension is still at `3.10.x`. The `^3.0.0` range resolves safely to the latest `3.x` for each package.
242
+
243
+ ---
244
+
245
+ ## Testing the Registry
246
+
247
+ `FeatureRegistry` exposes `unregister()` and `clear()` for test isolation:
248
+
249
+ ```ts
250
+ import { featureRegistry } from "@matops/editor";
251
+
252
+ afterEach(() => featureRegistry.clear());
253
+
254
+ test("custom feature resolves correctly", () => {
255
+ featureRegistry.register({ name: "my-feature", extensions: [] });
256
+ const { resolvedFeatures } = featureRegistry.resolve(["my-feature"]);
257
+ expect(resolvedFeatures).toContain("my-feature");
258
+ });
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Development Roadmap
264
+
265
+ | Phase | Features | Status |
266
+ |---|---|---|
267
+ | **Phase 0** | Core architecture, feature registry, config builder | ✅ Complete |
268
+ | **Phase 1** | Bubble menu, floating menu, slash command | 🔜 Next |
269
+ | **Phase 2** | Character count, word limit, AI placeholder | Planned |
270
+ | **Phase 3** | Full collaboration UI (cursors, presence) | Planned |
271
+ | **Phase 4** | Find & replace, export panel | Planned |
272
+
273
+ ---
274
+
275
+ ## License
276
+
277
+ MIT © Matops